﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

using Storm.TextEditor.Win32;

namespace Storm.TextEditor.Interacting.Scrolling
{
	/// <summary>
	/// Represents a handler for scrolling a TextEditor with the middle mouse button.
	/// </summary>
	[ToolboxItem(false)]
	public class ScrollHandler
		: Control
	{
		#region Fields

		private bool active = false;
		private bool dirty  = false;

		private Point activationPoint = new Point(0, 0);
		private Point startPoint      = new Point(0, 0);
		private Point currentDelta    = new Point(0, 0);

		private WeakTimer     feedbackTimer = null;
		private WeakReference currentParent = null;

		private Bitmap        image           = null;
		private PictureBox    pictureImage    = null;
		private RegionHandler regionHandler   = null;
		private Color         transparencyKey = Color.FromArgb(255, 0, 255);

		private IContainer components;

		#region Events

		/// <summary>
		/// Occurs when scrolling begins.
		/// </summary>
		public event EventHandler BeginScroll;

		/// <summary>
		/// Occurs when scrolling ends.
		/// </summary>
		public event EventHandler EndScroll;

		/// <summary>
		/// Occurs when the user is scrolling.
		/// </summary>
		public event ScrollEventHandler Scroll;

		#endregion

		#endregion

		#region Win32

		protected const int WM_MBUTTONDOWN    = 0x0207;
		protected const int WM_MBUTTONUP      = 0x0208;
		protected const int WM_LBUTTONDOWN    = 0x0201;
		protected const int WM_RBUTTONDOWN    = 0x0204;
		protected const int WM_CAPTURECHANGED = 0x0215;
		protected const int WM_MOUSELEAVE     = 0x02A3;

		#endregion

		#region Properties

		/// <summary>
		/// Gets the current Parent Control of the ScrollHandler.
		/// </summary>
		public Control CurrentParent
		{
			get
			{
				if (currentParent != null)
					return currentParent.Target as Control;

				return null;
			}
			set { currentParent = new WeakReference(value); }
		}

		/// <summary>
		/// Gets or sets the Image of the ScrollHandler.
		/// </summary>
		public Bitmap Image
		{
			get { return image; }
			set
			{
				image = value;
				dirty = true;
			}
		}

		/// <summary>
		/// Gets or sets the transparency key of the ScrollHandler.
		/// </summary>
		public Color TransparencyKey
		{
			get { return transparencyKey; }
			set
			{
				transparencyKey = value;
				dirty = true;
			}
		}

		#endregion

		#region Methods

		#region Protected

		protected override void Dispose(bool disposing)
		{
			if (disposing == true)
			{
				if (components != null)
					components.Dispose();
			}

			base.Dispose(disposing);
		}

		/// <summary>
		/// Raises the BeginScroll event.
		/// </summary>
		/// <param name="e">EventArgs.</param>
		protected virtual void OnBeginScroll(EventArgs e)
		{
			if (this.BeginScroll != null)
				this.BeginScroll(this, e);
		}

		/// <summary>
		/// Raises the EndScroll event.
		/// </summary>
		/// <param name="e">EventArgs.</param>
		protected virtual void OnEndScroll(EventArgs e)
		{
			if (this.EndScroll != null)
				this.EndScroll(this, e);
		}

		/// <summary>
		/// Raises the Scroll event.
		/// </summary>
		/// <param name="e">EventArgs.</param>
		protected virtual void OnScroll(ScrollEventArgs e)
		{
			if (this.Scroll != null)
				this.Scroll(this, e);
		}

		protected override void OnMouseDown(MouseEventArgs e)
		{
			base.OnMouseDown(e);
			if (active == true)
			{
				if (e.Button != MouseButtons.None && e.Button != MouseButtons.Middle && e.X != 0 && e.Y != 0)
				{
					this.Deactivate();
					Point p = new Point(e.X + Left, e.Y + Top);
					NativeUser32Api.SendMessage(this.Parent.Handle, WM_LBUTTONDOWN, 0, p.Y * 0x10000 + p.X);
				}
			}
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			if (active == true)
			{
				if (e.Button != MouseButtons.Middle && e.Button != MouseButtons.None)
					this.Deactivate();
				else
				{
					int x = e.X;
					int y = e.Y;

					x -= this.Image.Width / 2;
					y -= this.Image.Height / 2;

					this.SetCursor(x, y);
					NativeUser32Api.SendMessage(this.Handle, WM_MBUTTONDOWN, 0, 0);
				}
			}
			else
			{
				base.OnMouseMove(e);
			}
		}

		protected override void OnResize(EventArgs e)
		{
			if (this.Image != null)
				this.Size = new Size(this.Image.Width, this.Image.Height);
			else
				this.Size = new Size(32, 32);
		}

		protected override void OnKeyDown(KeyEventArgs e)
		{
			this.Deactivate();
		}

		protected override void OnLeave(EventArgs e)
		{
			this.Deactivate();
			base.OnLeave(e);
		}

		protected override void OnLostFocus(EventArgs e)
		{
			this.Deactivate();
			base.OnLostFocus(e);
		}

		protected override void WndProc(ref Message m)
		{
			if (m.Msg == WM_MOUSELEAVE)
				this.Deactivate();

			base.WndProc(ref m);
		}

		#endregion

		#region Public

		/// <summary>
		/// Actives the ScrollHandler at the given coordinates.
		/// </summary>
		/// <param name="x">X coordinate.</param>
		/// <param name="y">Y coordinate.</param>
		public void Activate(int x, int y)
		{
			if (dirty == true)
				this.CreateRegion();

			this.Location = new Point(x - this.Image.Width / 2, y - this.Image.Height / 2);
			this.Size = new Size(this.Image.Width, this.Image.Height);
			activationPoint = new Point(x, y);

			this.Visible = true;
			this.BringToFront();
			this.Focus();

			active = false;
			Application.DoEvents();
			this.SetCursor(0, 0);
			feedbackTimer.Enabled = true;
			this.OnBeginScroll(new EventArgs());
			NativeUser32Api.SendMessage(this.Handle, WM_MBUTTONDOWN, 0, 0);
			active = true;
		}

		#endregion

		#region Private

		private void CreateRegion()
		{
			regionHandler.ApplyRegion(this, this.Image, this.TransparencyKey);
			dirty = true;
		}

		private void SetCursor(int x, int y)
		{
			Assembly assembly = GetType().Assembly;

			int dY = y;
			int dX = x;

			currentDelta.X = dX;
			currentDelta.Y = dY;

			if (dY > 16)
			{
				Cursor = new Cursor(assembly.GetManifestResourceStream("Storm.TextEditor.Resources.MoveDown.cur"));
				currentDelta.Y -= 16;
			}
			else if (dY < -16)
			{
				Cursor = new Cursor(assembly.GetManifestResourceStream("Storm.TextEditor.Resources.MoveUp.cur"));
				currentDelta.Y += 16;
			}
			else
			{
				Stream strm = assembly.GetManifestResourceStream("Storm.TextEditor.Resources.MoveUpDown.cur");
				Cursor = new Cursor(strm);
				currentDelta = new Point(0, 0);
			}
		}

		private void Deactivate()
		{
			NativeUser32Api.SendMessage(Handle, WM_MBUTTONUP, 0, 0);

			active = false;
			feedbackTimer.Enabled = false;

			this.Hide();
			this.OnEndScroll(new EventArgs());
			this.Parent.Focus();
		}

		private void OnParentMouseDown(object s, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Middle)
				this.Activate(e.X, e.Y);
		}

		private void OnTimerTick(object sender, EventArgs e)
		{
			ScrollEventArgs a = new ScrollEventArgs();
			a.DeltaX = currentDelta.X;
			a.DeltaY = currentDelta.Y;

			this.OnScroll(a);
		}

		private void OnParentChanged(object sender, EventArgs e)
		{
			if (this.CurrentParent != null)
				this.CurrentParent.MouseDown -= OnParentMouseDown;

			if (this.Parent != null)
			{
				this.Parent.MouseDown += OnParentMouseDown;
				this.Deactivate();
			}
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes a new instance of ScrollHandler.
		/// </summary>
		public ScrollHandler()
		{
			InitializeComponent();
		}

		#region Component Designer generated code

		/// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		protected void InitializeComponent()
		{
			components = new Container();
			pictureImage = new PictureBox();
			feedbackTimer = new WeakTimer(components);
			regionHandler = new RegionHandler(components);
			((System.ComponentModel.ISupportInitialize)(pictureImage)).BeginInit();
			SuspendLayout();
			// 
			// pictureImage
			// 
			pictureImage.Location = new System.Drawing.Point(17, 17);
			pictureImage.Name = "pictureImage";
			pictureImage.Size = new System.Drawing.Size(100, 50);
			pictureImage.TabIndex = 0;
			pictureImage.TabStop = false;
			// 
			// feedbackTimer
			// 
			feedbackTimer.Enabled = true;
			feedbackTimer.Interval = 10;
			feedbackTimer.Tick += OnTimerTick;
			// 
			// regionHandler
			// 
			regionHandler.Control = null;
			regionHandler.MaskImage = null;
			regionHandler.TransparencyKey = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(0)))), ((int)(((byte)(255)))));
			// 
			// IntelliMouseControl
			// 
			ParentChanged += OnParentChanged;
			((System.ComponentModel.ISupportInitialize)(pictureImage)).EndInit();
			ResumeLayout(false);

		}

		#endregion
	}
}
